#include <stdexcept>
#include <iostream>

#include <memory.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

template <class T, int32_t max_size>
RShareMemArray<T, max_size>::RShareMemArray(const char *name): max_count_(max_size) {
    max_element_size_ = max_count_ * sizeof(T);
    mmap_size_ = max_element_size_ + sizeof(shm_t) - 1;

    shm_fd_ = shm_open(name, O_RDWR, S_IRUSR | S_IWUSR);
    if (shm_fd_ == -1) {
        if (errno == ENOENT) {
            shm_fd_ = shm_open(name, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
            if (shm_fd_ == -1) {
                perror("shm_open failed");
                throw std::runtime_error("failed call shm_open 2");
            }
        } else {
            perror("shm_open failed");
            throw std::runtime_error("failed call shm_open 1");
        }
    }

    if (-1 == ftruncate(shm_fd_, mmap_size_)) {
        close(shm_fd_);
        shm_unlink(name);
        throw std::runtime_error("ftruncate return -1");
    }

    mem_ = (shm_t *)mmap(NULL, mmap_size_, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd_, 0);
    if (mem_ == MAP_FAILED) {
        close(shm_fd_);
        shm_unlink(name);
        throw std::runtime_error("mmap failed");
    }

    name_ = strdup(name);

    mem_->count = 0;

    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
#ifdef ADAPTIVE_MUTEX
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP);
#endif
    pthread_mutex_init(&mem_->mutex, &attr);
    pthread_mutexattr_destroy(&attr);
}

template <class T, int32_t max_size>
RShareMemArray<T, max_size>::~RShareMemArray() {
    munmap(mem_, max_element_size_);
    close(shm_fd_);
    free(name_);
}

template <class T, int32_t max_size>
int32_t RShareMemArray<T, max_size>::Add(T *element) {
    pthread_mutex_lock(&mem_->mutex);
    if (mem_->count >= max_count_) {
        pthread_mutex_unlock(&mem_->mutex);
        return -1;
    }

    size_t size = sizeof(T);

    memcpy(&mem_->data[mem_->count * size], element, size);
    int32_t index = mem_->count++;
    pthread_mutex_unlock(&mem_->mutex);

    return index;
}

template <class T, int32_t max_size>
int RShareMemArray<T, max_size>::Remove(int32_t index) {
    if (index >= max_count_) return -1;

    pthread_mutex_lock(&mem_->mutex);
    memset(&mem_->data[index], 0, sizeof(T));
    pthread_mutex_unlock(&mem_->mutex);

    return 0;
}

template <class T, int32_t max_size>
void RShareMemArray<T, max_size>::Unlink() {
    shm_unlink(name_);
}

template <class T, int32_t max_size>
int32_t RShareMemArray<T, max_size>::Length() {
    return mem_->count;
}

template <class T, int32_t max_size>
T *RShareMemArray<T, max_size>::operator[](int32_t index) {
    if (index >= max_count_)
        return NULL;
    else
        return reinterpret_cast<T *>(mem_->data + index * sizeof(T));
}
